﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace PacManLib
{
    /// <summary>
    /// Plays a tournament of all available AIs against all others.
    /// The tournament can be played over a number of rounds.
    /// </summary>
    public class Tournament
    {
        #region Public types

        /// <summary>
        /// Holds the total score for the tournament for one AI.
        /// </summary>
        public class AIScore
        {
            // Constructor...
            public AIScore()
            {
                AIName = "";
                PacManScore = 0;
                GhostScore = 0;
                Games = 0;
            }

            // The name of the AI...
            public string AIName { get; set; }

            // Pac-Man's score for this AI...
            public int PacManScore { get; set; }

            // The ghost's score for this AI...
            public int GhostScore { get; set; }

            // The total score...
            public int TotalScore { get { return PacManScore + GhostScore; } }

            // The time in ms that the AI took to play the game...
            public double MS { get; set; }

            // The score / time taken...
            public double ScorePerMS { get { return TotalScore / MS; } }

            // The number of games played by this AI...
            public int Games { get; set; }
        }

        #endregion

        #region Events

        /// <summary>
        /// Data passed with events raised by this class.
        /// </summary>
        public class Args : EventArgs
        {
            // The current game board...
            public Board Board { get; set; }
        }

        /// <summary>
        /// Event raised when one game in the tournament is over.
        /// </summary>
        public event EventHandler<Args> GameOverEvent;

        /// <summary>
        /// Event raised after each turn has been played.
        /// </summary>
        public event EventHandler<Args> TurnPlayedEvent;

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public Tournament()
        {
        }

        /// <summary>
        /// Plays a tournament.
        /// </summary>
        public void play(int rounds)
        {
            for (int i = 0; i < rounds; ++i)
            {
                playOneRound();
            }
        }

        /// <summary>
        /// Returns the scores.
        /// </summary>
        public IList<AIScore> getScores()
        {
            return m_mapAINamesToScores.Values.ToList();
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Plays one round of the tournament. A round plays all AIs
        /// against all other AIs.
        /// </summary>
        private void playOneRound()
        {
            // We loop over all combinations of Pac-Man, ghosts 1&2 and
            // ghosts 3&4...
            string[] pacManAINames = AIManager.getInstance().getPacManAINames();
            string[] ghostAINames = AIManager.getInstance().getGhostAINames();
            foreach (string pacManAI in pacManAINames)
            {
                foreach (string ghostAI in ghostAINames)
                {
                    playGame(pacManAI, ghostAI);
                }
            }
        }

        /// <summary>
        /// Plays a game using the AIs passed in.
        /// </summary>
        private void playGame(string pacManAI, string ghostAI)
        {
            // We create a Game object and set the AIs to use...
            Game game = new Game();
            AIManager aiManager = AIManager.getInstance();
            game.setPacManAI(aiManager.getNewPacManAIInstance(pacManAI));
            game.setGhostAI(0, aiManager.getNewGhostAIInstance(ghostAI));
            game.setGhostAI(1, aiManager.getNewGhostAIInstance(ghostAI));
            game.setGhostAI(2, aiManager.getNewGhostAIInstance(ghostAI));
            game.setGhostAI(3, aiManager.getNewGhostAIInstance(ghostAI));

            // We play turns of the game until we get the game-over event...
            game.GameOverEvent += onGameOverEvent;
            m_gameOver = false;
            while (m_gameOver == false)
            {
                game.playOneTurn();
                Utility.raiseEvent(TurnPlayedEvent, this, new Args { Board = game.Board });
            }
            game.GameOverEvent -= onGameOverEvent;

            // We update the scores and raise an event to say that the game is over and
            // to report the scores...
            updateScores(pacManAI, ghostAI, game);
            Utility.raiseEvent(GameOverEvent, this, null);
        }

        /// <summary>
        /// We update the scores for the various players from the game passed in
        /// (which should have ended).
        /// </summary>
        private void updateScores(string pacManAI, string ghostAI, Game game)
        {
            AIScore pacManScore = getScore(pacManAI);
            pacManScore.PacManScore += game.getPacManScore();
            pacManScore.Games++;
            pacManScore.MS += (game.getPacManTicks() / (double)Stopwatch.Frequency) * 1000.0;

            AIScore ghostScore = getScore(ghostAI);
            
            ghostScore.GhostScore += game.getGhostScore(0);
            ghostScore.GhostScore += game.getGhostScore(1);
            ghostScore.GhostScore += game.getGhostScore(2);
            ghostScore.GhostScore += game.getGhostScore(3);

            ghostScore.Games++;

            ghostScore.MS += (game.getGhostTicks(0) / (double)Stopwatch.Frequency) * 1000.0;
            ghostScore.MS += (game.getGhostTicks(1) / (double)Stopwatch.Frequency) * 1000.0;
            ghostScore.MS += (game.getGhostTicks(2) / (double)Stopwatch.Frequency) * 1000.0;
            ghostScore.MS += (game.getGhostTicks(3) / (double)Stopwatch.Frequency) * 1000.0;
        }

        /// <summary>
        /// Returns the score for the player passed in.
        /// </summary>
        private AIScore getScore(string player)
        {
            AIScore score;
            if (m_mapAINamesToScores.ContainsKey(player))
            {
                score = m_mapAINamesToScores[player];
            }
            else
            {
                score = new AIScore();
                score.AIName = player;
                m_mapAINamesToScores[player] = score;
            }
            return score;
        }

        /// <summary>
        /// Called when we get the game-over event.
        /// </summary>
        private void onGameOverEvent(object sender, Game.Args e)
        {
            m_gameOver = true;
        }

        #endregion

        #region Private data

        // Set to true when a game has completed...
        private bool m_gameOver = false;

        // A map of AI names to the scores for them...
        private Dictionary<string, AIScore> m_mapAINamesToScores = new Dictionary<string, AIScore>();

        #endregion
    }
}
